﻿/**
 * @file AutoComplete.js
 * @description
 *	bind input element typing for text suggest
 * $id:v1.0.0, 2009-03-02 14:03, baboo<istxwang@gmail.com>$
 */
(function ($) {

$.AutoComplete = $({
	__init: function (sets) {
		sets = $.extend({
			noResultText: 'No Result!',	
			hideWhenNoResult: true,
			$flashMaskLayer: false,
			className: 'auto-complete',
			classNameSelected: 'auto-complete-selected',
			delay: 1,
			hideDelay: 0.3,
			valueField: 0,
			useJson: false,
			noCache : false,
			getRetArr : function (j) { return j && j.result },
			getRetWord : function (j) { return j && j.word },
			getDataSource : false,
			strict: 1	//when strict == 1, before request send, the word will be checked if in no-result words prefix
		}, sets);
		var 
			//the text input element to bind typing
			$input = sets.$input, 
			//the suggest text container layer
			$show = sets.$show, 
			//to fixed the problem that flash is alway up the show layer
			//the flashMaskLayer is a block element that contians a iframe element
			$flashMaskLayer = sets.$flashMaskLayer,
			
			lastChoosed = null, 
			
			lastShowValue = null, lastValue = null, oriValue = null, timer = null, hideTimer = null, _t = this,
			//clear the suggest layer show timer
			clearTimer = function () {if (timer) clearTimeout(timer); timer = null},
			
			//set the suggest layer hide timer
			setHideTimer = function () {
				if ($show.css('display') == 'none') 
					return;
				clearHideTimer();
				hideTimer = setTimeout(hide, sets.hideDelay * 1000);	
			},
			
			//clear the suggest layer hide timer
			clearHideTimer = function () {
				if (hideTimer != null) {
					clearTimeout(hideTimer);	
					hideTimer = null;
				}
			};
		
		if (!($input && $input.length && $show && $show.length))
			throw "$.AutoComplete Sets required: $input and $show";
		if (sets.useJson && !sets.jsonCallbackName) {
			sets.jsonCallbackName = $.guid();
		}
 		
 		
		var onkeydownorup = function (event) { 
			var code = event.which;
			
			//for browsers that in keyup event can't capture 'down' key
			if (event.type == 'keydown' && code == $.Event.DOWN)
				return chooseNext();
				
			switch(code) {
				case $.Event.DOWN: 
					!$.isOpera && chooseNext();
  					return; 
				case $.Event.UP: 
					chooseNext(true);
					return;
				case $.Event.RETURN:
					hide();
					(sets.onPressEnter || $.nothing)();
 					return;
				 case $.Event.ESC: 
				 	hide();
				 	return;
				 default: 
				 	checkValue();
			}	
		}; 
		
		//input blur event handler
		var onblur = function () { 
			hide(); 
			clearTimer(); 
			$.AutoComplete.Mgr.activeInstance = null;
		};
		
		//tool function
		var v = function () { return $input.val().trim().toLowerCase() };  
	
		var checkValue = function () {
			var value = v();
			if (lastShowValue == value && $show.css('display') != 'none') return;
			lastValue = value;
			setHideTimer();
			if (lastValue == '' ||(sets.strict == 1 && $.AutoComplete.NoResultPrefix.has(lastValue))) {
				clearHideTimer();
				hide();
				return; 
			}
			$.AutoComplete.Mgr.activeInstance = _t;
			if (!sets.delay) 
				return show(lastValue);
			clearTimer ();
			timer = setTimeout(delayShow.bind(null, lastValue), sets.delay * 1000);	
		};
		
		var delayShow = function (w) { 
			if (w == v()) show(w);	
		}; 
		
		var show = function (w) {
			//clearHideTimer();
			//如果存在获取相关文本对象的接口函数，则通过该函数获取对象
			if (sets.getDataSource) {
				return realShow(sets.getDataSource(w));	
			}
			
			var url = sets.url.format(w.encode());
			if (sets.noCache) url  += '&rand=' + Math.random();
			
			//使用动态Script方法与服务器交互数据
			if (sets.useJson) {
				if (sets.jsonCallbackName) {
					if ( !window[sets.jsonCallbackName]) {
						window[sets.jsonCallbackName] = function ( j) {
							$.AutoComplete.Mgr.show(j);
						}
					}
					return new $.Json(url, {
						onOk : sets.jsonCallbackName
					});
				}
				return new $.Json(url, {
					onOk : realShow
				});
			}
			new $.Ajax(url, {
				method : 'get',
				evalJson: true,
				onSuccess: function (json) {
					realShow(json);	
				}	
			}); 
		} ; 
		var realShow = function (o) {
			var w = sets.getRetWord(o);
			var result = sets.getRetArr(o);
			if (!w || w != v() || !result) {
				return;
			}
			lastShowValue = w;
			lastChoosed = null;
			var sb = [];
			if (sets.prefix) sb.push(sets.prefix);
			if (result.length == 0) $.AutoComplete.NoResultPrefix.add(w);
			for (var i = 0; i < result.length; i ++) {
				sb.push(sets.process(result[i], i));	
			}
			if (result.length == 0) {
				if (sets.hideWhenNoResult) return;
				sb.push(sets.noResultText);
			}
			clearHideTimer();
			if (sets.suffix) sb.push(sets.suffix);
			$show.html(sb.join(''));
			($show.find('> *.' + sets.className) || []).each(
				function (e, i) { 
					e. title = result[i][sets.valueField];
					e.onmouseover = function() {choose(e, true)};
					e.onmousedown = function (){check(e.title)};	
				}
			);
			$show.show();
			if ($flashMaskLayer) {
				$flashMaskLayer.css('height:' + $show[0].offsetHeight + 'px').show();
				var $iframe = $flashMaskLayer.find('iframe');
				if ($iframe) $iframe.css('height:' + $show[0].offsetHeight + 'px');
			}
		};
		var hide = function () {
			if ($show.css('display') == 'none') return;
			if ($flashMaskLayer) $flashMaskLayer.hide();
			$show.hide();
			sets.onHide && sets.onHide();
			lastChoosed = null;
		};
		/**
		  *  public function
		  */
		var activated = false;
		this.activate = function () {
			if (activated) return;
			if ($.isOpera) {
				$input.keydown(onkeydownorup);
			} 
			$input.keyup(onkeydownorup); 
			$input.blur(onblur);
			activated = true;
			return this;
		};
		
		this.disable = function () {
			if (!activated) return;
			if ($.isOpera) {
				$input.$keydown(onkeydownorup);	
			} 
			$input.$keyup(onkeydownorup);	
			$input.$blur(onblur );
			clearTimer();
			activated = false;	
			return this;
		};	
		
		this.setUrlTpl = function (newUrl) {
			sets.url = newUrl;
			lastShowValue=null;
			lastValue=null;
			if (sets.strict == 1) $.AutoComplete.NoResultPrefix.length = 0;
			return this;
		};
		var check = function (w) {
			lastValue = w;
			$input.val(w);
			hide();	
			if (sets.onCheck)  sets.onCheck($input);
		};
		
		var unchoose = function (o) {
			$(o).className('-', sets.classNameSelected);
		}
; 
		var choose = function (o, doNotChangeValue) {
			if (lastChoosed) unchoose(lastChoosed);
			if (o === null) {
				lastChoosed = null;
				return $input.val(oriValue || '');
			}
			$(o).className(sets.classNameSelected);
			lastChoosed = o;
			if (!doNotChangeValue) {
				var w = o.title;
				lastValue = w.toLowerCase();
				$input.val(w);
			}
		};
		this.choose = choose;
		var chooseNext = function (up) { 
			if ($show.css('display') == 'none') return;
			var es = $show.find('.' + sets.className);
			if (!es.length) return;
			if (lastChoosed == null) {
				oriValue = $input.val();
				if (up) return choose(es.get().last());
				return choose(es[0]);
			}
			
			var i;
			for (i = 0; i < es.length; i ++) {
				if (lastChoosed == es[i]) break;
			}
			if (up) {
				if (i == 0) return choose(null);	
				return choose(es[--i]);
			}
			if (i == es.length - 1) return choose(null);
			return choose(es[++i]);
		} ;
		this.__show = realShow;
		this.activate();
	}
});
$.AutoComplete.NoResultPrefix = {
	list: [],
	add: function (w) { this.list.push(w) },
	has: function (w) {
		if(!w) return false;
		for (var i = 0; i < this.list.length; i ++) {
			if (w.indexOf(this.list[i]) == 0) return true;	
		}	
		return false;
	}
};
//为解决使用script对象与服务器交互数据，回调方法固定而实例有多个的问题
$.AutoComplete.Mgr = {
	activeInstance : null,
	show : function (j) {
		if (this.activeInstance) {
			this.activeInstance.__show(j);	
		}
	}
};

})($imple);